/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: io_gpio_mpc8308.c$
* $Version : 3.8.2.1$
* $Date    : Feb-13-2012$
*
* Comments:
*
*   This file contains the GPIO standard functions used on boards
*
*END************************************************************************/

#include "mqx.h"
#include "bsp.h"
#include "io_gpio.h"
#include "io_gpio_prv.h"
#include <string.h>

/* This structure will hold 'used pins'. If somebody would like to open 'already used pin',
** the operation will fail. All open pins whithin all GPIO driver files will be written here.
** We can predefine this structure with 'already used' bits for those bits, which are not
** used at all - so we will prevent open operation to be successful.
*/

/* Reserved pins = 1 */
GPIO_PIN_MAP  gpio_global_pin_map = {
    0x000000ff    
};

GPIO_PIN_MAP  gpio_global_irq_map = {
    0x000000ff    
};

extern GPIO_DEV_DATA_PTR first_irq; /* first file having used IRQ */


/*FUNCTION*****************************************************************
* 
* Function Name    : gpio_cpu_configure
* Returned Value   : IO_OK
* Comments         :
*    Configures pins to be GPIO based on pin map
*
*END*********************************************************************/
static void gpio_cpu_configure
   (
      /* [IN] pointer to file data */
      GPIO_DEV_DATA_PTR   dev_data_ptr
   )
{
   MPC83xx_GPIO_STRUCT_PTR gpio_ptr;
   uint_32                 immr;

   _PSP_GET_IMMR(immr);

   gpio_ptr = (MPC83xx_GPIO_STRUCT_PTR)(immr + MPC83xx_GPIO1_BASE);

   if (dev_data_ptr->type == DEV_OUTPUT) { 
     gpio_ptr->DIR |= dev_data_ptr->pin_map.memory32[0];
   } else { 
     gpio_ptr->DIR &= ~dev_data_ptr->pin_map.memory32[0];
   }
    
   /* set pin functionality to be GPIO for the whole file */
   /* pin, which we will use as GPIO = 1 in dev_data_ptr, GPIO functionality = 0 in 
   ** registers. Other pins funcionality is unchanged */

   // Set SICRH here 
}


/*FUNCTION****************************************************************
* 
* Function Name    : gpio_irq
* Returned Value   : 
* Comments         : GPIO IRQ handler
*    
*
*END*********************************************************************/
static void gpio_irq (pointer param) 
{
   GPIO_DEV_DATA_PTR       gptr = first_irq;
   MPC83xx_GPIO_STRUCT_PTR gpio_ptr;
   uint_32                 immr;

   _PSP_GET_IMMR(immr);

   gpio_ptr = (MPC83xx_GPIO_STRUCT_PTR)(immr + MPC83xx_GPIO1_BASE);
    
   while (gptr) { /* list through all irq files */
      if (gptr->irqp_map.memory32[0] & gpio_ptr->IER) {
         gptr->irq_func(NULL);
      }
      gptr = gptr->NEXT;
    }
   gpio_ptr->IER = 0xFFFFFFFF; /* clear flag(s) */
}


/*FUNCTION*****************************************************************
* 
* Function Name    : gpio_cpu_install
* Returned Value   : IO_OK
* Comments         :
*    Performs basic initialization specific to board
*
*END*********************************************************************/

_mqx_int gpio_cpu_init() 
{
    /* int_install_isr returns NULL when it fails, otherwise a vector address */
    if (_int_install_isr(MPC83xx_GPIO1_VECTOR, gpio_irq, NULL)) {
       return IO_OK;
   }

    return IO_ERROR;       
}


/*FUNCTION*****************************************************************
* 
* Function Name    : gpio_cpu_open
* Returned Value   : IO_OK or error
* Comments         :
*    Implements initialization for DATA GPIO
*
*END*********************************************************************/

_mqx_int gpio_cpu_open
   (
      /* [IN] the file handle for the device */
      MQX_FILE_PTR   fd_ptr,

      /* [IN] the file name */
      char_ptr   file_name,

      /* [IN] pointer to parameters */
      char_ptr   param_ptr
   )
{ 
   GPIO_DEV_DATA_PTR       dev_data_ptr = (GPIO_DEV_DATA_PTR) fd_ptr->DEV_DATA_PTR;
   _mqx_int                i;
   MPC83xx_GPIO_STRUCT_PTR gpio_ptr;
   uint_32                 immr;

   _PSP_GET_IMMR(immr);

   gpio_ptr = (MPC83xx_GPIO_STRUCT_PTR)(immr + MPC83xx_GPIO1_BASE);


   /* if file_name is used, then the user wants to open peripheral */
   if ((file_name != NULL) && (*file_name != 0)) {
        if (!strncmp(file_name, "gpio:write", 11))  /* user wants write access to GPIO */
            dev_data_ptr->type = DEV_OUTPUT;
        else if (!strncmp(file_name, "gpio:output", 12))  /* user wants write access to GPIO */
            dev_data_ptr->type = DEV_OUTPUT;
        else if (!strncmp(file_name, "gpio:read", 10)) /* user wants read access to GPIO */
            dev_data_ptr->type = DEV_INPUT;
        else if (!strncmp(file_name, "gpio:input", 11)) /* user wants read access to GPIO */
            dev_data_ptr->type = DEV_INPUT;
        else
        /* peripherals not used yet */
            return IO_ERROR;
   }

   if ((param_ptr != NULL) && (dev_data_ptr->type == DEV_OUTPUT)) { /* set pins status before selecting GPIO function */
      /* note that this is similar to GPIO_IOCTL_WRITE function, but no checking is performed
      (was done in io_gpio_open function) */
      GPIO_PIN_STRUCT _PTR_    pin_table;
      uint_32                  addr;
      uint_8                   pin;
      GPIO_PIN_MAP             temp_pin0_map = {0};
      GPIO_PIN_MAP             temp_pin1_map = {0};

      /* prepare pin map */
      for (pin_table = (GPIO_PIN_STRUCT _PTR_) param_ptr; *pin_table != GPIO_LIST_END; pin_table++) {
         addr = (*pin_table & GPIO_PIN_ADDR) >> 3; /* prepare address of port */ 
         pin = 1 << (7 - (*pin_table & 0x07));  /* prepare bit mask */
            if (*pin_table & GPIO_PIN_STATUS)
                temp_pin1_map.memory8[addr] |= pin;
            else
                temp_pin0_map.memory8[addr] |= pin;
      }
        
      /* save device pin map */
      dev_data_ptr->pin_map.memory32[0] = temp_pin0_map.memory32[0] | temp_pin1_map.memory32[0];

      /* ok, now we can apply new map, first set pin direction */
      gpio_cpu_configure(dev_data_ptr);

      /* apply entire map in one instruction */
       gpio_ptr->DATA &= ~temp_pin0_map.memory32[0];
       gpio_ptr->DATA |=  temp_pin1_map.memory32[0];
    }

    return IO_OK;
} 



/*FUNCTION*****************************************************************
* 
* Function Name    : gpio_cpu_ioctl
* Returned Value   : depends on IOCTL command
* Comments         :
*    Implements all ioctl for GPIO
*
*END*********************************************************************/

_mqx_int gpio_cpu_ioctl
   (
      /* [IN] the file handle for the device */
      MQX_FILE_PTR   fd_ptr,

      /* [IN] the ioctl command */
      _mqx_uint  cmd,

      /* [IN] the ioctl parameters */
      pointer    param_ptr
   )
{ 
   GPIO_DEV_DATA_PTR       dev_data_ptr = (GPIO_DEV_DATA_PTR) fd_ptr->DEV_DATA_PTR;
   GPIO_PIN_STRUCT _PTR_   pin_table;
   uint_32                 addr;
   uint_8                  pin;
   GPIO_PIN_MAP            temp_pin_map = {0};
   GPIO_PIN_MAP            temp_pin0_map = {0};
   GPIO_PIN_MAP            temp_pin1_map = {0};
   MPC83xx_GPIO_STRUCT_PTR gpio_ptr;
   uint_32                 immr;

   _PSP_GET_IMMR(immr);

   gpio_ptr = (MPC83xx_GPIO_STRUCT_PTR)(immr + MPC83xx_GPIO1_BASE);

   switch (cmd) {
      case GPIO_IOCTL_ADD_PINS: {
         
         /* check pin_table if they are not in global pin_map */
         _int_disable();
         for (pin_table = (GPIO_PIN_STRUCT _PTR_) param_ptr; *pin_table != GPIO_LIST_END; pin_table++) {
            if (*pin_table & GPIO_PIN_VALID) { /* check pin validity bit */
               addr = (*pin_table & GPIO_PIN_ADDR) >> 3; /* prepare address of port */ 
               pin = 1 << (*pin_table & 0x07);  /* prepare bit mask */
               if (addr < sizeof(GPIO_PIN_MAP)) /* pin address out of map scope? */
                  if (! (gpio_global_pin_map.memory8[addr] & pin)) /* pin address already used? */
                     continue; /* manage next pin */
             }
             /* some problem occured */
             _int_enable();
             return IO_ERROR;
         }
         /* check successful, now we have to copy these bits to local pin map */
         for (pin_table = (GPIO_PIN_STRUCT _PTR_) param_ptr; *pin_table != GPIO_LIST_END; pin_table++) {
            addr = (*pin_table & GPIO_PIN_ADDR)  >> 3; /* prepare address of port */ 
            pin = 1 << (*pin_table & 0x07);  /* prepare bit mask */
            dev_data_ptr->pin_map.memory8[addr] |= pin; /* mark pin as used by this file */
            gpio_global_pin_map.memory8[addr] |= pin; /* mark pin as used globally */
         }
         gpio_cpu_configure(dev_data_ptr);
         _int_enable();
      }
      break;

      case GPIO_IOCTL_WRITE_LOG1: {
         /* If this file is configured as output, sets mapped pins. */
         if (dev_data_ptr->type == DEV_OUTPUT) {
             _int_disable();
             gpio_ptr->DATA |=   dev_data_ptr->pin_map.memory32[0];
             _int_enable();
         } else {
             return IO_ERROR;
         }
      }
      break;

      case GPIO_IOCTL_WRITE_LOG0: {
         /* If this file is configured as output, clears mapped pins. */
         if (dev_data_ptr->type == DEV_OUTPUT) {
            _int_disable();
            gpio_ptr->DATA &= ~dev_data_ptr->pin_map.memory32[0];
            _int_enable();
         } else {
             return IO_ERROR;
         }
      }
      break;

      case GPIO_IOCTL_WRITE: {
         /* If this file is configured as output, sets every pin to the state defined in GPIO_PIN_STATUS. Params:
            list of pin addresses to be used or NULL for every pin in the file */
         if (dev_data_ptr->type != DEV_OUTPUT) {
            return IO_ERROR;	
         }
         if (param_ptr == NULL) {/* no param specified , but must be */
            return IO_ERROR;
         } else {

            _int_disable();
            /* check if there is not pin in the list which this file does not contain */
            for (pin_table = (GPIO_PIN_STRUCT _PTR_) param_ptr; *pin_table != GPIO_LIST_END; pin_table++) {
               if (*pin_table & GPIO_PIN_VALID) { /* check pin validity bit */
                  addr = (*pin_table & GPIO_PIN_ADDR) >> 3; /* prepare address of port */ 
                  pin = 1 << (*pin_table & 0x07);  /* prepare bit mask */
                  if (addr < sizeof(GPIO_PIN_MAP)) {/* pin address out of map scope? */
                     if (dev_data_ptr->pin_map.memory8[addr] & pin) { /* pin address already used? */
                        if (*pin_table & GPIO_PIN_STATUS) {
                           temp_pin1_map.memory8[addr] |= pin;
                        } else {
                           temp_pin0_map.memory8[addr] |= pin;
                        }
                        continue; /* manage next pin */
                     }
                  }
               }
               /* some problem occured */
               _int_enable();
               return IO_ERROR;
            }
            /* ok, now we can apply new map */
            /* note: applying the map after collecting pins is due to have pins applied in one instruction */
            gpio_ptr->DATA &= ~temp_pin0_map.memory32[0];
            gpio_ptr->DATA |=  temp_pin1_map.memory32[0];
            _int_enable();
         }
      }
      break;

      case GPIO_IOCTL_READ: {
         /* Read status of pins, if configured as input. Params: pointer to predefined table where data will be written */

         if (dev_data_ptr->type != DEV_INPUT)
            return IO_ERROR;
         if (param_ptr == NULL) /* no list defined */
            return IO_ERROR;
         _int_disable();
         /* check if there is not pin in the list which this file does not contain */
         for (pin_table = (GPIO_PIN_STRUCT _PTR_) param_ptr; *pin_table != GPIO_LIST_END; pin_table++) {
            if (*pin_table & GPIO_PIN_VALID) { /* check pin validity bit */
               addr = (*pin_table & GPIO_PIN_ADDR) >> 3; /* prepare address of port */ 
               pin = 1 << (*pin_table & 0x07);  /* prepare bit mask */
               if (addr < sizeof(GPIO_PIN_MAP)) { /* pin address out of map scope? */
                  if (dev_data_ptr->pin_map.memory8[addr] & pin) { /* pin address  used? */
                     uint_32 mask;
                     mask = pin <<(8*(3-addr));
                     if (gpio_ptr->DATA & mask) { /* check pin status */
                        *pin_table |= GPIO_PIN_STATUS; /* set pin status to 1 in the list */
                     } else {
                        *pin_table &= ~GPIO_PIN_STATUS; /* clear pin status to 0 in the list */
                     }
                     continue; /* manage next pin */
                  }
               }
            }
            /* some problem occured */
            _int_enable();
            return IO_ERROR;
         }
         _int_enable();
      }
      break;

      case GPIO_IOCTL_SET_IRQ_FUNCTION: {
         if (dev_data_ptr->type == DEV_OUTPUT)
            return IO_ERROR; /* cannot set IRQ for output devices */

         dev_data_ptr->irq_func = param_ptr;
         _int_disable();
            
         if (param_ptr == NULL) {  /* remove function from list */
            if (first_irq != NULL) {
               GPIO_DEV_DATA_PTR gptr = first_irq;
               while (gptr->NEXT != NULL) {
                  if (gptr == dev_data_ptr)
                     break;
                  if (gptr->NEXT == dev_data_ptr)
                     break;
                  gptr = gptr->NEXT;
               }
               if (gptr == dev_data_ptr) /* found as first in the list */
                  first_irq = first_irq->NEXT;
               else if (gptr->NEXT == dev_data_ptr) /* found in the middle or at the end of list */
                  gptr->NEXT = gptr->NEXT->NEXT;
            }
         } else { /* add new function to the list */
            if (first_irq == NULL) {
               first_irq = dev_data_ptr;
            } else {
               GPIO_DEV_DATA_PTR gptr = first_irq;
               while (gptr->NEXT != NULL) {
                  if (gptr == dev_data_ptr)
                      break;
                  gptr = gptr->NEXT;
               }
               if (gptr != dev_data_ptr)
                  gptr->NEXT = dev_data_ptr;
            }
         }
         _int_enable();
      }
      break;

      default:
         return IO_ERROR_INVALID_IOCTL_CMD;
   }
        
   return IO_OK;

}
